package mahjong;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.io.Serializable;

import javax.swing.*;
import java.awt.event.*;

/**
 * Actual board component of the GUI. 
 */
@SuppressWarnings("serial")
public class Viewer extends JPanel implements Serializable
{
	/** Tile width. */
	private static int tileW = 32;
	/** Tile height. */
	private static int tileH = 88;
	/** Tile shift due to increased level. */
	private static int tileSkew = 6;
	/** Width of overlap to squeeze tiles together */
	private static int tileWOverlap =0;
	/** Height of overlap to squeeze tiles together*/
	private static int tileHOverlap =0;
	/** Tile images. */
	private Image[][] tileImages;
	/** Highlighted tile images. */
	private Image[][] tileImagesHL;
	/** Board itself. */
	private Board b;
	/** Canvases that will hold tile images and handle clicks*/
	//private TileCanvas[] canvases;
	/** Background on which the boards to lie */
	private Image felt;
	/** Win image */
	private Image win;
	/** Lose image */
	private Image lose;
	/** Paused image */
	private Image paused;
	/** player acting on viewer */
	private Player player;
	/** game over flag */
	private boolean gameover;
	/** game started flag */
	private boolean gameStarted = false;
	/** game paused flag */
	private boolean gamePaused = false;
	
	/** Hint Mode */
	private boolean hintMode = false;
	/**
	 * Constructor, creates a viewer for the given board.
	 * @param b1 The board to display.
	 * @param p The player acting on the viewer
	 */
	public Viewer(Board b1, Player p) 
	{
		super(true);
		this.setLayout(new BorderLayout());
		b = b1;
		player = p;
		makeTileImages(Board.MAXGROUPS);
		this.setLayout(new BorderLayout()); 
		this.addMouseListener(new TileMouseListener());
			
	}

	/**
	 * Loads the images for a single set of tiles.
	 * @param groups Number of groups to load.
	 * @param append The String common to each tile image file name.
	 * @return A 2D array of Image objects, providing images for 
	 * each subindex member of each group.
	 */
	private Image[][] loadTileSet(int groups,String append) 
	{
		Image[][] tr = new Image[groups][Board.GROUPSIZE];
		Toolkit toolkit = Toolkit.getDefaultToolkit();
		MediaTracker mediaTracker = new MediaTracker(this);
		try 
		{
			BufferedReader f = new BufferedReader(new FileReader(Main.getTileSetFile()));
			String s = f.readLine();
			while (s!=null) 
			{
				if (!s.startsWith("#") && ! s.startsWith("%")) 
				{
					String[] tokens = s.split("[ ]+");
					try 
					{
						int gn = Integer.parseInt(tokens[0]);
						for (int i = 1; i<=(Board.GROUPSIZE); i++) 
						{
							Image img = toolkit.getImage(Main.getTileDir()+append+tokens[i]+".png");
							mediaTracker.addImage(img, 0);
							try 
							{
								mediaTracker.waitForID(0);
							} catch (InterruptedException ie) {
								System.err.println(ie);
								System.exit(1);
							}
							tr[gn][i-1] = img;
						}
					} 
					catch (NumberFormatException n) 
					{
						System.out.println("Error reading group count: "+s);
						System.exit(1);
					}
				}
				s = f.readLine();
			}
		} 
		catch (IOException ie) 
		{
			System.out.println("IOException: "+ ie);
			ie.printStackTrace();
			System.exit(1);
		}
		return tr;
	}

	/**Loads tileDimensions values for the tileset.
	 * @return A 1D array of integers with format
	 * [width, height, skew, width overlap, height overlap]
	 */	
	private int[] loadTileDims() 
	{
		int vals[] = new int[5];
		try {
			BufferedReader f = new BufferedReader(new FileReader(Main.getTileSetFile()));
			String s = f.readLine();
			while (s!=null) 
			{
				if ( s.startsWith("%")) {
					String[] tokens = s.split("%");
					try {
						vals[0] = Integer.parseInt(tokens[1].replace(" ", ""));
						vals[1] = Integer.parseInt(tokens[2].replace(" ", ""));
						vals[2] = Integer.parseInt(tokens[3].replace(" ",""));
						vals[3] = Integer.parseInt(tokens[4].replace(" ", ""));
						vals[4] = Integer.parseInt(tokens[5].replace(" ",""));
					}
					// set default values if format is wrong
					catch (NumberFormatException n) 
					{
						vals[0] = 32;
						vals[1] = 88;
						vals[2] = 6;
						vals[3] = 0;
						vals[4] = 0;
						//System.exit(1);
					}
				}
				s = f.readLine();
			}
		} 
		// set default values if file not found
		catch (IOException ie) 
		{
			vals[0] = 32;
			vals[1] = 88;
			vals[2] = 6;
			vals[3] = 0;
			vals[4] = 0;
		}
		return vals;
	}

	/** For setting size of drawing area in gui. 
	 * @return The preferred size. */
	public Dimension getPreferredSize() 
	{
		int maxw = b.getWidth();
		int maxh = b.getHeight();
		maxw = tileW*(maxw-1);
		maxh = tileH/2*(maxh+1);
		return new Dimension(maxw,maxh);
	}


	/** Load images for the given number of groups. 
	 * also loads the tile overlap values
	 * and the win and loose illustrations
	 * @param groups The number of groups in the tile set. */
	private void makeTileImages(int groups) 
	{
		Toolkit toolkit = Toolkit.getDefaultToolkit();
		this.setDims(loadTileDims()); 
		this.setWin(toolkit.getImage(Main.getTileDir()+"winner.png"));
		this.setLose(toolkit.getImage(Main.getTileDir()+"loser.png"));
		this.setPaused(toolkit.getImage(Main.getTileDir()+"paused.png"));
		//felt = toolkit.getImage(Main.getTileDir()+"Background"+java.io.File.separator+ "green_felt.gif");
		tileImages = loadTileSet(groups,  "unlit" + java.io.File.separator + "unlit_");
		tileImagesHL = loadTileSet(groups,"lit" + java.io.File.separator + "lit_");
		
	}

	/**
	 * Resets the game state.
	 */
	public void reset(int i)
	{
		GameTime.stopTimer();
		gamePaused = false;
		gameStarted = false;
		gameover = false;
		hintMode = false;

		b.resetGame(i);
		
	}

	/**Used to set custom tile dimension for paintning
	 * @param vals int array of dimensions
	 * [width,height,skew,width overlap, height overlap]
	 */
	public void setDims(int[] vals)
	{
		setTileW(vals[0]);
		setTileH(vals[1]);
		setTileSkew(vals[2]);
		setTileWOverlap(vals[3]);
		setTileHOverlap(vals[4]);
	}

	/** The paintComponent method is called whenever this component needs to be 
	 * repainted. 
	 * @param graphics The Graphics object. */
	public void paintComponent(Graphics graphics) 
	{
		// let superclass paint to fill in background
		super.paintComponent(graphics);		
		Tile[] content = b.getContent().clone();
		// Paint the tiles in order
		Arrays.sort(content, new PaintOrderComparator());

		if (content==null || tileImages==null) {
			return;
		}


		if(!gamePaused)
		{
		
			for(int i=0; i < content.length; i++) {			
				Tile t = content[i];
				int posX, posY;			
				Image image;
				if (t!=null && !t.isRemoved()) {
					image = tileImages[t.getValue()][t.getSubindex()];
					if (t.canRemove()) {
						// highlight exposed tile
						image = tileImagesHL[t.getValue()][t.getSubindex()];
					}
					posX = t.getX()*(getTileW()-getTileWOverlap())+getTileW()/2+t.getZ()*getTileSkew();
					posY = (t.getY()+1)*(getTileH()-getTileHOverlap())/2-t.getZ()*getTileSkew();
					graphics.drawImage(image, posX,posY,this);
					if (t.returnHint() == 1){
						graphics.setColor(new Color(0,0,255, 100));
						graphics.fillRoundRect(posX+6,posY,getTileW()*2-7,getTileH()-8,5,5);
					}
					if(t.returnHint() == 2)
					{
						graphics.setColor(new Color(255,0,0, 100));
						graphics.fillRoundRect(posX+6,posY,getTileW()*2-7,getTileH()-8,5,5);
					}
					if(t.isAISelected())
					{
						graphics.setColor(new Color(0,255,0, 100));
						graphics.fillRoundRect(posX+6,posY,getTileW()*2-7,getTileH()-8,5,5);
					}
					if (t.isSelected()) {
						// make selected tiles visually apparent
						graphics.setColor(Color.ORANGE);
						graphics.drawRoundRect(posX+6,posY,getTileW()*2-7,getTileH()-8,5,5);
						graphics.drawRoundRect(posX+7,posY+1,getTileW()*2-9,getTileH()-10,5,5);
					}
				}	
			}
		}else
		{
			int posX = this.getWidth()/2 - win.getWidth(this)/2;
			int posY = this.getHeight()/2 - win.getHeight(this)/2;
			graphics.drawImage(paused,posX,posY,this);
		}

		
		
		if(b.isEmpty()) {
			//Paint Winner!
			gameover = true;
			GameTime.stopTimer();
			int posX = this.getWidth()/2 - win.getWidth(this)/2;
			int posY = this.getHeight()/2 - win.getHeight(this)/2;
			graphics.drawImage(win,posX,posY,this);
		}

		if(b.isDeadLock()) {
			//Paint loser!
			gameover = true;
			GameTime.stopTimer();
			int posX = this.getWidth()/2 - win.getWidth(this)/2;
			int posY = this.getHeight()/2 - win.getHeight(this)/2;
			graphics.drawImage(lose,posX,posY,this);
		}
	}

	/** Get tile width.
	 * @return tile width.
	 */
	public static int getTileW()
	{
		return tileW;
	}

	/** Set tile width.
	 * @param tileW tile width.
	 */
	private static void setTileW(int tileW)
	{
		Viewer.tileW = tileW;
	}

	/** Get tile height.
	 * @return tile height.
	 */
	public static int getTileH() 
	{
		return tileH;
	}

	/** Set tile height.
	 * @param tileW tile height.
	 */
	private static void setTileH(int tileH) 
	{
		Viewer.tileH = tileH;
	}

	/** Get tile skew, the offest used to draw tiles on higher levels..
	 * @return tile skew
	 */
	public static int getTileSkew() 
	{
		return tileSkew;
	}

	/** Set tile skew.
	 * @param tileSkew tile skew
	 */
	private static void setTileSkew(int tileSkew) 
	{
		Viewer.tileSkew = tileSkew;
	}

	/** Get tile width overlap.
	 * @return tile width overlap.
	 */
	public static int getTileWOverlap() 
	{
		return tileWOverlap;
	}

	/** Set tile width overlap.
	 * @param tileWOverlap tile width overlap.
	 */
	private static void setTileWOverlap(int tileWOverlap) 
	{
		Viewer.tileWOverlap = tileWOverlap;
	}

	/** Get tile height overlap.
	 * @return tile height overlap.
	 */
	public static int getTileHOverlap() 
	{
		return tileHOverlap;
	}

	/** Set tile height overlap.
	 * @param tileHOverlap tile height overlap.
	 */
	private static void setTileHOverlap(int tileHOverlap) 
	{
		Viewer.tileHOverlap = tileHOverlap;
	}

	/** Set winning image.
	 * @param win the winning image.
	 */
	public void setWin(Image win) 
	{
		this.win = win;
	}

	/** Set losing image.
	 * @param lose the losing image.
	 */
	public void setLose(Image lose) 
	{
		this.lose = lose;
	}
	
	/** Set paused image.
	 * 
	 */
	public void setPaused(Image paused)
	{
		this.paused = paused;
	}

	/**Helper method for the mouseEvent handler, locates the clicked tile
	 * at level <tt>z</tt> given the coordinates of the mouse click.
	 * 
	 * @param mouseX X coord of mouse
	 * @param mouseY Y coord of mouse
	 * @param z depth to check at
	 * @return tile at that location null is spot for depth is empty
	 */
	private Tile getTileAt(int mouseX, int mouseY, int z)
	{
		Tile[] tiles = b.getContent();
		int tileX, tileY;     
		for (int i = tiles.length -1; i>=0; i--) {
			Tile t = tiles[i];
			tileX =  t.getX()*(getTileW()-getTileWOverlap())+getTileW()/2+t.getZ()*getTileSkew();
			tileY = (t.getY()+1)*(getTileH()-getTileHOverlap())/2-t.getZ()*getTileSkew();
			if (tiles[i].getZ() == z && !tiles[i].isRemoved()) {
				if (mouseX - tileX < 2*getTileW() && mouseX - tileX >= 0 &&
						mouseY - tileY < getTileH() && mouseY - tileY >=0)
					return t;
			}	
		}        
		return null;
	}


	/** Helper class to handle mouse events on tiles */
	class TileMouseListener implements MouseListener
	{

		@Override
		public void mouseClicked(MouseEvent e) {
			// TODO Auto-generated method stub
			//
		}

		@Override
		public void mouseEntered(MouseEvent e) {
			// TODO Auto-generated method stub

		}

		@Override
		public void mouseExited(MouseEvent e) {
			// TODO Auto-generated method stub

		}

		@Override
		public void mousePressed(MouseEvent e)
		{
			if (gameover || gamePaused || b.aiPlaying() ) return;
			int depth = b.getDepth();
			Tile selectedT = null;
			// from top to bottom, find the tile clicked
			while (depth > 0) {
				selectedT = getTileAt(e.getX(), e.getY(), depth-1);
				if (selectedT != null ) {
					if (!gameStarted && selectedT.canRemove()) {
						gameStarted = true;
						GameTime.startTimer();
					}
					player.selectTile(selectedT);
					break;
				}
				depth--;                        
			}
		}

		@Override
		public void mouseReleased(MouseEvent e)
		{
			// TODO Auto-generated method stub

		}
	}

	/**
	 * Toggle hint mode on and off
	 */
	public void switchHintMode()
	{
		hintMode = !hintMode;
		b.hint(hintMode);
	}
	
	/**
	 * return true if hint mode is on
	 */
	public boolean isHintMode()
	{
		return hintMode;
	}
	
	/**
	 * Autoplay board for testing purposes
	 */
	/*
	public void autoPlay()
	{
		b.autoPlay();
		repaint();
	}
	*/
	/**
	 * undo the last played move
	 */
	public void undo()
	{
		b.undo();
		gameover = false;
	}

	/**
	 * redo the move previously undone
	 */
	public void redo()
	{
		b.redo();
	}
	/**
	 * Pause the game
	 */
	public void pause()
	{
		GameTime.stopTimer();
		gamePaused = true;
	}

	/**
	 * Continue a paused game
	 */
	public void cont()
	{
		gamePaused = false;
		GameTime.startTimer();
	}
	
	/**
	 * Return the current board
	 * @return current board 
	 */
	public Board getBoard()
	{
		return b;
	}
	
	/**
	 * Set board to a given board
	 * @param board board to be set
	 */
	public void setBoard(Board board)
	{
		b = board;			
	}
}
